Skip to content

Conversation

@Lpsd
Copy link
Member

@Lpsd Lpsd commented Jan 14, 2026

Summary

This update significantly improves the performance and memory efficiency of MTA's CEF browser implementation.

Non-blocking render thread synchronization

The most impactful change is replacing the old blocking synchronization model, where CEF's render thread would wait up to 250ms for the main thread, with External Begin Frame scheduling that allows MTA to request frames on-demand in sync with the game loop.


Memory optimizations

Memory consumption is reduced by switching browser textures from D3DPOOL_MANAGED to D3DPOOL_DEFAULT with D3DUSAGE_DYNAMIC, eliminating the duplicate system RAM copy that managed textures require.


Resource allocation improvements

Lazy browser creation defers CEF subprocess spawning until the first URL is actually loaded, avoiding resource allocation for unused browser instances.


Enable GPU compositing

GPU compositing and hardware video decoding are now properly enabled when the GPU setting is on, which drastically improves YouTube and video playback performance.

This also adds a new setting "Enable video acceleration" to the web browser settings menu.

!! IMPORTANT NOTE !!

Previously GPU compositing was unconditionally disabled due to previous stability concerns in release builds - this must be thoroughly tested in both nightly and official release streams


Mouse input/polling throttle

Additionally, mouse input throttling limits move events from being completely unbounded, to around 60 per second, reducing unnecessary CEF repaints while maintaining smooth cursor tracking.


Together, these changes reduce CPU overhead, lower memory footprint, improve video playback, and provide more stable frame rates.

Additionally, I've provided a more detailed summary of each change below.

The "changes made" sections provide a detailed/technical overview of the notable changes.
The "benefits" sections provide a higher level summary of what has changed, and why.

External Begin Frame Scheduling

Changes made:

CWebView.h:

  • Changed m_RenderData.buffer from const void* to std::unique_ptr<byte[]> (owned buffer)
  • Added bufferSize member to track allocation

CWebView.cpp:

  • Modified OnPaint() to copy the buffer immediately instead of storing a pointer and blocking
  • Simplified UpdateTexture() by removing all CV signaling code
  • Updated SetRenderingPaused() to use .reset() for owned buffer
  • Enabled windowInfo.external_begin_frame_enabled = true in Initialise()
  • Removed ResumeCefThread() calls from destructor, CloseBrowser(), and Resize()
  • Removed ResumeCefThread() function definition

CWebCore.cpp:

  • Modified DoEventQueuePulse() to call SendExternalBeginFrame() for each active browser before UpdateTexture()

Benefits:

  • Eliminates the 250ms blocking wait in OnPaint
  • CEF now renders on-demand when MTA's render loop requests it via SendExternalBeginFrame()
  • Buffer ownership is clearer - we own a copy, not a pointer to CEF's internal buffer
  • Reduced synchronization overhead between CEF render thread and main thread

Use D3DPOOL_DEFAULT with D3DUSAGE_DYNAMIC

Changes made to CRenderItem.WebBrowser.cpp:

CreateUnderlyingData():

  • Changed from D3DXCreateTexture with D3DPOOL_MANAGED to CreateTexture with D3DPOOL_DEFAULT + D3DUSAGE_DYNAMIC
  • Removed D3DX dependency (using native D3D9 API)

OnLostDevice():

  • Now calls ReleaseUnderlyingData() (required for DEFAULT pool)

OnResetDevice():

  • Now calls CreateUnderlyingData() (required for DEFAULT pool)

Changes made to CWebView.cpp:

  • LockRect now uses D3DLOCK_DISCARD flag for optimal dynamic texture updates

Benefits:

  • No system memory copy, compared to managed textures which kept a system memory backup (previously doubling memory usage)
  • Driver can give us a new buffer immediately instead of waiting for GPU
  • Better for frequent updates - dynamic textures are optimized for Lock/Unlock patterns
  • Reduced memory footprint - Only VRAM is used, not VRAM + system RAM

Lazy Browser Creation

Changes made:

CWebView.h:

  • Added m_bBrowserCreated flag to track if CEF browser has been created
  • Added EnsureBrowserCreated() method declaration

CWebView.cpp:

  • Initialise() now does nothing (deferred creation)
  • Added EnsureBrowserCreated() - creates the CEF browser with all settings on first call
  • LoadURL() now calls EnsureBrowserCreated() before loading

Benefits:

  • Browsers that are created but never navigate don't allocate CEF process resources
  • Pre-allocated browser UI elements don't spawn CEF processes until needed
  • No idle CEF renderer processes for unused browsers

Video Decode Acceleration & GPU Compositing

Changes made:

CClientVariables.cpp:

  • Added browser_enable_video_acceleration CVar with default true

CSettings.h:

  • Added m_pCheckBoxBrowserVideoAccelEnabled member variable

CSettings.cpp:

  • Created "Enable video acceleration" checkbox below GPU rendering checkbox
  • Load/save the video acceleration setting
  • Include setting change in restart prompt check

CWebApp.cpp:

  • Added enable-accelerated-video-decode switch when GPU and video accel are enabled
  • disable-gpu-compositing is now conditional (when GPU disabled), not unconditional
  • This restores GPU compositing as an optional setting when GPU is also enabled.

Benefits:

  • YouTube and other video content uses GPU for decoding
  • CEF can now use GPU for page compositing when GPU is enabled
  • New settings option to disable video acceleration if it causes issues
  • User is prompted to restart when settings change to ensure application

Mouse Input Throttling

Changes made:

CWebView.h:

  • Added m_vecPendingMousePosition - stores pending throttled position
  • Added m_bHasPendingMouseMove - tracks if there's a pending move
  • Added m_lastMouseMoveTime - for throttle timing

CWebView.cpp:

  • InjectMouseMove() now throttles to ~60Hz (16ms interval)
  • Pending moves are stored and sent on next interval or before clicks
  • InjectMouseDown() flushes any pending mouse move before click for accurate positioning

Benefits:

  • At 500 FPS, mouse moves were sent 500 times/sec, now limited to ~60/sec (still smooth for user perception)
  • Fewer mouse move events = fewer CEF internal repaints
  • Pending position is flushed before click events (accuracy improvement)

Motivation

After some recent discussions surrounding main menu revamp / GUI changes in the development discord (see #main-menu-revamp), and testing CEF myself, I noticed the performance in certain areas was quite poor (e.g YouTube videos choppy, inconsistent framerates, etc).

Many servers already rely on CEF for the majority of their UI, so we must ensure it runs as smooth as possible, especially if we're even going to consider using CEF ourselves for main menu UI (which some users do seem interested in).

Test plan

Here's a small CEF test resource which implements a tiny CEGUI-based browser window, with the ability to go back/forward, refresh & enter URLs directly into the 'address bar': cef.zip

Just load the resource, and you'll see instructions in chat. This also tests the lazy loading functionality.
Compared to builds without these improvements, I'm seeing a 10-20% FPS improvement in some cases, frame times are more consistent, and video playback is much smoother.

There are some really good tests on this site which actually cover some specific test cases based on the changes made above: https://frameratetest.com/

For example https://frameratetest.com/mouse-polling-rate-test/ can be used to ensure the mouse polling changes are properly enforcing the 60hz limit.

There's various sites like this out there anyway - just have a browse around and compare to builds without these changes and you're sure to see improvements.

Well, if you're still not convinced, check out the video examples below. The first is from MTA's official release (latest) - and the second is from my custom build with these changes (both fps_limit 999 on server + client). Even after multiple compressions of the video files you can still clearly see the difference - it's even easier to notice when you test the changes yourself!

MTA official release

https://www.youtube.com/watch?v=mGIOUSHIFGQ

Custom build

https://www.youtube.com/watch?v=hZljFFxa2H0

Checklist

  • Your code should follow the coding guidelines.
  • Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

Lpsd added 30 commits May 24, 2024 10:35
@Lpsd Lpsd requested a review from a team as a code owner January 14, 2026 07:52
@DmitriyColeman
Copy link
Contributor

DmitriyColeman commented Jan 14, 2026

Good job!

MegadreamsBE
MegadreamsBE previously approved these changes Jan 14, 2026
@Xenius97
Copy link
Contributor

Xenius97 commented Jan 14, 2026

Would it be possible to add the codecs to this CEF version? I’m thinking of the fact that, for example, YouTube live streams currently don’t work for this reason. These codec packs are not included in minimal CEF build. (H.264 & AAC)

@Lpsd
Copy link
Member Author

Lpsd commented Jan 14, 2026

Would it be possible to add the codecs to this CEF version? I’m thinking of the fact that, for example, YouTube live streams currently don’t work for this reason. These codec packs are not included in minimal CEF build. (H.264 & AAC)

This is unlikely due to licensing, we can't distribute those codecs.

@Kinimel
Copy link

Kinimel commented Jan 14, 2026

Good job, I can't wait for this to be implemented in new versions.

Dirty rect 'optimizations' were actually lowering performance, and causing edge case issues with device restoration and partial updates - removed this functionality. Also improved device reset/restore handling (force repaint)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants